/* **************************************************************************
 * $OpenLDAP: /com/novell/sasl/client/DigestMD5SaslClient.java,v 1.4 2005/01/17 15:00:54 sunilk Exp $
 *
 * Copyright (C) 2003 Novell, Inc. All Rights Reserved.
 *
 * THIS WORK IS SUBJECT TO U.S. AND INTERNATIONAL COPYRIGHT LAWS AND
 * TREATIES. USE, MODIFICATION, AND REDISTRIBUTION OF THIS WORK IS SUBJECT
 * TO VERSION 2.0.1 OF THE OPENLDAP PUBLIC LICENSE, A COPY OF WHICH IS
 * AVAILABLE AT HTTP://WWW.OPENLDAP.ORG/LICENSE.HTML OR IN THE FILE "LICENSE"
 * IN THE TOP-LEVEL DIRECTORY OF THE DISTRIBUTION. ANY USE OR EXPLOITATION
 * OF THIS WORK OTHER THAN AS AUTHORIZED IN VERSION 2.0.1 OF THE OPENLDAP
 * PUBLIC LICENSE, OR OTHER PRIOR WRITTEN CONSENT FROM NOVELL, COULD SUBJECT
 * THE PERPETRATOR TO CRIMINAL AND CIVIL LIABILITY.
 ******************************************************************************/
package com.novell.sasl.client;

import org.apache.harmony.javax.security.sasl.*;
import org.apache.harmony.javax.security.auth.callback.*;
import java.security.SecureRandom;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.io.UnsupportedEncodingException;
import java.io.IOException;
import java.util.*;

/**
 * Implements the Client portion of DigestMD5 Sasl mechanism.
 */
public class DigestMD5SaslClient implements SaslClient {
	private String m_authorizationId = "";
	private String m_protocol = "";
	private String m_serverName = "";
	private Map m_props;
	private CallbackHandler m_cbh;
	private int m_state;
	private String m_qopValue = "";
	private char[] m_HA1 = null;
	private String m_digestURI;
	private DigestChallenge m_dc;
	private String m_clientNonce = "";
	private String m_realm = "";
	private String m_name = "";

	private static final int STATE_INITIAL = 0;
	private static final int STATE_DIGEST_RESPONSE_SENT = 1;
	private static final int STATE_VALID_SERVER_RESPONSE = 2;
	private static final int STATE_INVALID_SERVER_RESPONSE = 3;
	private static final int STATE_DISPOSED = 4;

	private static final int NONCE_BYTE_COUNT = 32;
	private static final int NONCE_HEX_COUNT = 2 * NONCE_BYTE_COUNT;

	private static final String DIGEST_METHOD = "AUTHENTICATE";

	/**
	 * Creates an DigestMD5SaslClient object using the parameters supplied.
	 * Assumes that the QOP, STRENGTH, and SERVER_AUTH properties are contained
	 * in props
	 * 
	 * @param authorizationId
	 *            The possibly null protocol-dependent identification to be used
	 *            for authorization. If null or empty, the server derives an
	 *            authorization ID from the client's authentication credentials.
	 *            When the SASL authentication completes successfully, the
	 *            specified entity is granted access.
	 * 
	 * @param protocol
	 *            The non-null string name of the protocol for which the
	 *            authentication is being performed (e.g. "ldap")
	 * 
	 * @param serverName
	 *            The non-null fully qualified host name of the server to
	 *            authenticate to
	 * 
	 * @param props
	 *            The possibly null set of properties used to select the SASL
	 *            mechanism and to configure the authentication exchange of the
	 *            selected mechanism. See the Sasl class for a list of standard
	 *            properties. Other, possibly mechanism-specific, properties can
	 *            be included. Properties not relevant to the selected mechanism
	 *            are ignored.
	 * 
	 * @param cbh
	 *            The possibly null callback handler to used by the SASL
	 *            mechanisms to get further information from the
	 *            application/library to complete the authentication. For
	 *            example, a SASL mechanism might require the authentication ID,
	 *            password and realm from the caller. The authentication ID is
	 *            requested by using a NameCallback. The password is requested
	 *            by using a PasswordCallback. The realm is requested by using a
	 *            RealmChoiceCallback if there is a list of realms to choose
	 *            from, and by using a RealmCallback if the realm must be
	 *            entered.
	 * 
	 * @return A possibly null SaslClient created using the parameters supplied.
	 *         If null, this factory cannot produce a SaslClient using the
	 *         parameters supplied.
	 * 
	 * @exception SaslException
	 *                If a SaslClient instance cannot be created because of an
	 *                error
	 */
	public static SaslClient getClient(String authorizationId, String protocol,
			String serverName, Map props, CallbackHandler cbh) {
		String desiredQOP = (String) props.get(Sasl.QOP);
		String desiredStrength = (String) props.get(Sasl.STRENGTH);
		String serverAuth = (String) props.get(Sasl.SERVER_AUTH);

		// only support qop equal to auth
		if ((desiredQOP != null) && !"auth".equals(desiredQOP))
			return null;

		// doesn't support server authentication
		if ((serverAuth != null) && !"false".equals(serverAuth))
			return null;

		// need a callback handler to get the password
		if (cbh == null)
			return null;

		return new DigestMD5SaslClient(authorizationId, protocol, serverName,
				props, cbh);
	}

	/**
	 * Creates an DigestMD5SaslClient object using the parameters supplied.
	 * Assumes that the QOP, STRENGTH, and SERVER_AUTH properties are contained
	 * in props
	 * 
	 * @param authorizationId
	 *            The possibly null protocol-dependent identification to be used
	 *            for authorization. If null or empty, the server derives an
	 *            authorization ID from the client's authentication credentials.
	 *            When the SASL authentication completes successfully, the
	 *            specified entity is granted access.
	 * 
	 * @param protocol
	 *            The non-null string name of the protocol for which the
	 *            authentication is being performed (e.g. "ldap")
	 * 
	 * @param serverName
	 *            The non-null fully qualified host name of the server to
	 *            authenticate to
	 * 
	 * @param props
	 *            The possibly null set of properties used to select the SASL
	 *            mechanism and to configure the authentication exchange of the
	 *            selected mechanism. See the Sasl class for a list of standard
	 *            properties. Other, possibly mechanism-specific, properties can
	 *            be included. Properties not relevant to the selected mechanism
	 *            are ignored.
	 * 
	 * @param cbh
	 *            The possibly null callback handler to used by the SASL
	 *            mechanisms to get further information from the
	 *            application/library to complete the authentication. For
	 *            example, a SASL mechanism might require the authentication ID,
	 *            password and realm from the caller. The authentication ID is
	 *            requested by using a NameCallback. The password is requested
	 *            by using a PasswordCallback. The realm is requested by using a
	 *            RealmChoiceCallback if there is a list of realms to choose
	 *            from, and by using a RealmCallback if the realm must be
	 *            entered.
	 * 
	 */
	private DigestMD5SaslClient(String authorizationId, String protocol,
			String serverName, Map props, CallbackHandler cbh) {
		m_authorizationId = authorizationId;
		m_protocol = protocol;
		m_serverName = serverName;
		m_props = props;
		m_cbh = cbh;

		m_state = STATE_INITIAL;
	}

	/**
	 * Determines if this mechanism has an optional initial response. If true,
	 * caller should call evaluateChallenge() with an empty array to get the
	 * initial response.
	 * 
	 * @return true if this mechanism has an initial response
	 */
	public boolean hasInitialResponse() {
		return false;
	}

	/**
	 * Determines if the authentication exchange has completed. This method may
	 * be called at any time, but typically, it will not be called until the
	 * caller has received indication from the server (in a protocol- specific
	 * manner) that the exchange has completed.
	 * 
	 * @return true if the authentication exchange has completed; false
	 *         otherwise.
	 */
	public boolean isComplete() {
		if ((m_state == STATE_VALID_SERVER_RESPONSE)
				|| (m_state == STATE_INVALID_SERVER_RESPONSE)
				|| (m_state == STATE_DISPOSED))
			return true;
		else
			return false;
	}

	/**
	 * Unwraps a byte array received from the server. This method can be called
	 * only after the authentication exchange has completed (i.e., when
	 * isComplete() returns true) and only if the authentication exchange has
	 * negotiated integrity and/or privacy as the quality of protection;
	 * otherwise, an IllegalStateException is thrown.
	 * 
	 * incoming is the contents of the SASL buffer as defined in RFC 2222
	 * without the leading four octet field that represents the length. offset
	 * and len specify the portion of incoming to use.
	 * 
	 * @param incoming
	 *            A non-null byte array containing the encoded bytes from the
	 *            server
	 * @param offset
	 *            The starting position at incoming of the bytes to use
	 * 
	 * @param len
	 *            The number of bytes from incoming to use
	 * 
	 * @return A non-null byte array containing the decoded bytes
	 * 
	 */
	public byte[] unwrap(byte[] incoming, int offset, int len)
			throws SaslException {
		throw new IllegalStateException(
				"unwrap: QOP has neither integrity nor privacy>");
	}

	/**
	 * Wraps a byte array to be sent to the server. This method can be called
	 * only after the authentication exchange has completed (i.e., when
	 * isComplete() returns true) and only if the authentication exchange has
	 * negotiated integrity and/or privacy as the quality of protection;
	 * otherwise, an IllegalStateException is thrown.
	 * 
	 * The result of this method will make up the contents of the SASL buffer as
	 * defined in RFC 2222 without the leading four octet field that represents
	 * the length. offset and len specify the portion of outgoing to use.
	 * 
	 * @param outgoing
	 *            A non-null byte array containing the bytes to encode
	 * @param offset
	 *            The starting position at outgoing of the bytes to use
	 * @param len
	 *            The number of bytes from outgoing to use
	 * 
	 * @return A non-null byte array containing the encoded bytes
	 * 
	 * @exception SaslException
	 *                if incoming cannot be successfully unwrapped.
	 * 
	 * @exception IllegalStateException
	 *                if the authentication exchange has not completed, or if
	 *                the negotiated quality of protection has neither integrity
	 *                nor privacy.
	 */
	public byte[] wrap(byte[] outgoing, int offset, int len)
			throws SaslException {
		throw new IllegalStateException(
				"wrap: QOP has neither integrity nor privacy>");
	}

	/**
	 * Retrieves the negotiated property. This method can be called only after
	 * the authentication exchange has completed (i.e., when isComplete()
	 * returns true); otherwise, an IllegalStateException is thrown.
	 * 
	 * @param propName
	 *            The non-null property name
	 * 
	 * @return The value of the negotiated property. If null, the property was
	 *         not negotiated or is not applicable to this mechanism.
	 * 
	 * @exception IllegalStateException
	 *                if this authentication exchange has not completed
	 */
	public Object getNegotiatedProperty(String propName) {
		if (m_state != STATE_VALID_SERVER_RESPONSE)
			throw new IllegalStateException(
					"getNegotiatedProperty: authentication exchange not complete.");

		if (Sasl.QOP.equals(propName))
			return "auth";
		else
			return null;
	}

	/**
	 * Disposes of any system resources or security-sensitive information the
	 * SaslClient might be using. Invoking this method invalidates the
	 * SaslClient instance. This method is idempotent.
	 * 
	 * @exception SaslException
	 *                if a problem was encountered while disposing of the
	 *                resources
	 */
	public void dispose() throws SaslException {
		if (m_state != STATE_DISPOSED) {
			m_state = STATE_DISPOSED;
		}
	}

	/**
	 * Evaluates the challenge data and generates a response. If a challenge is
	 * received from the server during the authentication process, this method
	 * is called to prepare an appropriate next response to submit to the
	 * server.
	 * 
	 * @param challenge
	 *            The non-null challenge sent from the server. The challenge
	 *            array may have zero length.
	 * 
	 * @return The possibly null reponse to send to the server. It is null if
	 *         the challenge accompanied a "SUCCESS" status and the challenge
	 *         only contains data for the client to update its state and no
	 *         response needs to be sent to the server. The response is a
	 *         zero-length byte array if the client is to send a response with
	 *         no data.
	 * 
	 * @exception SaslException
	 *                If an error occurred while processing the challenge or
	 *                generating a response.
	 */
	public byte[] evaluateChallenge(byte[] challenge) throws SaslException {
		byte[] response = null;

		// printState();
		switch (m_state) {
		case STATE_INITIAL:
			if (challenge.length == 0)
				throw new SaslException("response = byte[0]");
			else
				try {
					response = createDigestResponse(challenge)
							.getBytes("UTF-8");
					m_state = STATE_DIGEST_RESPONSE_SENT;
				} catch (java.io.UnsupportedEncodingException e) {
					throw new SaslException(
							"UTF-8 encoding not suppported by platform", e);
				}
			break;
		case STATE_DIGEST_RESPONSE_SENT:
			if (checkServerResponseAuth(challenge))
				m_state = STATE_VALID_SERVER_RESPONSE;
			else {
				m_state = STATE_INVALID_SERVER_RESPONSE;
				throw new SaslException("Could not validate response-auth "
						+ "value from server");
			}
			break;
		case STATE_VALID_SERVER_RESPONSE:
		case STATE_INVALID_SERVER_RESPONSE:
			throw new SaslException("Authentication sequence is complete");
		case STATE_DISPOSED:
			throw new SaslException("Client has been disposed");
		default:
			throw new SaslException("Unknown client state.");
		}

		return response;
	}

	/**
	 * This function takes a 16 byte binary md5-hash value and creates a 32
	 * character (plus a terminating null character) hex-digit representation of
	 * binary data.
	 * 
	 * @param hash
	 *            16 byte binary md5-hash value in bytes
	 * 
	 * @return 32 character (plus a terminating null character) hex-digit
	 *         representation of binary data.
	 */
	char[] convertToHex(byte[] hash) {
		int i;
		byte j;
		byte fifteen = 15;
		char[] hex = new char[32];

		for (i = 0; i < 16; i++) {
			// convert value of top 4 bits to hex char
			hex[i * 2] = getHexChar((byte) ((hash[i] & 0xf0) >> 4));
			// convert value of bottom 4 bits to hex char
			hex[(i * 2) + 1] = getHexChar((byte) (hash[i] & 0x0f));
		}

		return hex;
	}

	/**
	 * Calculates the HA1 portion of the response
	 * 
	 * @param algorithm
	 *            Algorith to use.
	 * @param userName
	 *            User being authenticated
	 * @param realm
	 *            realm information
	 * @param password
	 *            password of teh user
	 * @param nonce
	 *            nonce value
	 * @param clientNonce
	 *            Clients Nonce value
	 * 
	 * @return HA1 portion of the response in a character array
	 * 
	 * @exception SaslException
	 *                If an error occurs
	 */
	char[] DigestCalcHA1(String algorithm, String userName, String realm,
			String password, String nonce, String clientNonce)
			throws SaslException {
		byte[] hash;

		try {
			MessageDigest md = MessageDigest.getInstance("MD5");

			md.update(userName.getBytes("UTF-8"));
			md.update(":".getBytes("UTF-8"));
			md.update(realm.getBytes("UTF-8"));
			md.update(":".getBytes("UTF-8"));
			md.update(password.getBytes("UTF-8"));
			hash = md.digest();

			if ("md5-sess".equals(algorithm)) {
				md.update(hash);
				md.update(":".getBytes("UTF-8"));
				md.update(nonce.getBytes("UTF-8"));
				md.update(":".getBytes("UTF-8"));
				md.update(clientNonce.getBytes("UTF-8"));
				hash = md.digest();
			}
		} catch (NoSuchAlgorithmException e) {
			throw new SaslException("No provider found for MD5 hash", e);
		} catch (UnsupportedEncodingException e) {
			throw new SaslException(
					"UTF-8 encoding not supported by platform.", e);
		}

		return convertToHex(hash);
	}

	/**
	 * This function calculates the response-value of the response directive of
	 * the digest-response as documented in RFC 2831
	 * 
	 * @param HA1
	 *            H(A1)
	 * @param serverNonce
	 *            nonce from server
	 * @param nonceCount
	 *            8 hex digits
	 * @param clientNonce
	 *            client nonce
	 * @param qop
	 *            qop-value: "", "auth", "auth-int"
	 * @param method
	 *            method from the request
	 * @param digestUri
	 *            requested URL
	 * @param clientResponseFlag
	 *            request-digest or response-digest
	 * 
	 * @return Response-value of the response directive of the digest-response
	 * 
	 * @exception SaslException
	 *                If an error occurs
	 */
	char[] DigestCalcResponse(char[] HA1, /* H(A1) */
			String serverNonce, /* nonce from server */
			String nonceCount, /* 8 hex digits */
			String clientNonce, /* client nonce */
			String qop, /* qop-value: "", "auth", "auth-int" */
			String method, /* method from the request */
			String digestUri, /* requested URL */
			boolean clientResponseFlag) /* request-digest or response-digest */
	throws SaslException {
		byte[] HA2;
		byte[] respHash;
		char[] HA2Hex;

		// calculate H(A2)
		try {
			MessageDigest md = MessageDigest.getInstance("MD5");
			if (clientResponseFlag)
				md.update(method.getBytes("UTF-8"));
			md.update(":".getBytes("UTF-8"));
			md.update(digestUri.getBytes("UTF-8"));
			if ("auth-int".equals(qop)) {
				md.update(":".getBytes("UTF-8"));
				md.update("00000000000000000000000000000000".getBytes("UTF-8"));
			}
			HA2 = md.digest();
			HA2Hex = convertToHex(HA2);

			// calculate response
			md.update(new String(HA1).getBytes("UTF-8"));
			md.update(":".getBytes("UTF-8"));
			md.update(serverNonce.getBytes("UTF-8"));
			md.update(":".getBytes("UTF-8"));
			if (qop.length() > 0) {
				md.update(nonceCount.getBytes("UTF-8"));
				md.update(":".getBytes("UTF-8"));
				md.update(clientNonce.getBytes("UTF-8"));
				md.update(":".getBytes("UTF-8"));
				md.update(qop.getBytes("UTF-8"));
				md.update(":".getBytes("UTF-8"));
			}
			md.update(new String(HA2Hex).getBytes("UTF-8"));
			respHash = md.digest();
		} catch (NoSuchAlgorithmException e) {
			throw new SaslException("No provider found for MD5 hash", e);
		} catch (UnsupportedEncodingException e) {
			throw new SaslException(
					"UTF-8 encoding not supported by platform.", e);
		}

		return convertToHex(respHash);
	}

	/**
	 * Creates the intial response to be sent to the server.
	 * 
	 * @param challenge
	 *            Challenge in bytes recived form the Server
	 * 
	 * @return Initial response to be sent to the server
	 */
	private String createDigestResponse(byte[] challenge) throws SaslException {
		char[] response;
		StringBuffer digestResponse = new StringBuffer(512);
		int realmSize;

		m_dc = new DigestChallenge(challenge);

		m_digestURI = m_protocol + "/" + m_serverName;

		if ((m_dc.getQop() & DigestChallenge.QOP_AUTH) == DigestChallenge.QOP_AUTH)
			m_qopValue = "auth";
		else
			throw new SaslException("Client only supports qop of 'auth'");

		// get call back information
		Callback[] callbacks = new Callback[3];
		ArrayList realms = m_dc.getRealms();
		realmSize = realms.size();
		if (realmSize == 0) {
			callbacks[0] = new RealmCallback("Realm");
		} else if (realmSize == 1) {
			callbacks[0] = new RealmCallback("Realm", (String) realms.get(0));
		} else {
			callbacks[0] = new RealmChoiceCallback("Realm",
					(String[]) realms.toArray(new String[realmSize]), 0, // the
																			// default
																			// choice
																			// index
					false); // no multiple selections
		}

		callbacks[1] = new PasswordCallback("Password", false);
		// false = no echo

		if (m_authorizationId == null || m_authorizationId.length() == 0)
			callbacks[2] = new NameCallback("Name");
		else
			callbacks[2] = new NameCallback("Name", m_authorizationId);

		try {
			m_cbh.handle(callbacks);
		} catch (UnsupportedCallbackException e) {
			throw new SaslException("Handler does not support"
					+ " necessary callbacks", e);
		} catch (IOException e) {
			throw new SaslException("IO exception in CallbackHandler.", e);
		}

		if (realmSize > 1) {
			int[] selections = ((RealmChoiceCallback) callbacks[0])
					.getSelectedIndexes();

			if (selections.length > 0)
				m_realm = ((RealmChoiceCallback) callbacks[0]).getChoices()[selections[0]];
			else
				m_realm = ((RealmChoiceCallback) callbacks[0]).getChoices()[0];
		} else
			m_realm = ((RealmCallback) callbacks[0]).getText();

		m_clientNonce = getClientNonce();

		m_name = ((NameCallback) callbacks[2]).getName();
		if (m_name == null)
			m_name = ((NameCallback) callbacks[2]).getDefaultName();
		if (m_name == null)
			throw new SaslException("No user name was specified.");

		m_HA1 = DigestCalcHA1(m_dc.getAlgorithm(), m_name, m_realm, new String(
				((PasswordCallback) callbacks[1]).getPassword()),
				m_dc.getNonce(), m_clientNonce);

		response = DigestCalcResponse(m_HA1, m_dc.getNonce(), "00000001",
				m_clientNonce, m_qopValue, "AUTHENTICATE", m_digestURI, true);

		digestResponse.append("username=\"");
		digestResponse.append(m_authorizationId);
		if (0 != m_realm.length()) {
			digestResponse.append("\",realm=\"");
			digestResponse.append(m_realm);
		}
		digestResponse.append("\",cnonce=\"");
		digestResponse.append(m_clientNonce);
		digestResponse.append("\",nc=");
		digestResponse.append("00000001"); // nounce count
		digestResponse.append(",qop=");
		digestResponse.append(m_qopValue);
		digestResponse.append(",digest-uri=\"");
		digestResponse.append(m_digestURI);
		digestResponse.append("\",response=");
		digestResponse.append(response);
		digestResponse.append(",charset=utf-8,nonce=\"");
		digestResponse.append(m_dc.getNonce());
		digestResponse.append("\"");

		return digestResponse.toString();
	}

	/**
	 * This function validates the server response. This step performs a modicum
	 * of mutual authentication by verifying that the server knows the user's
	 * password
	 * 
	 * @param serverResponse
	 *            Response recived form Server
	 * 
	 * @return true if the mutual authentication succeeds; else return false
	 * 
	 * @exception SaslException
	 *                If an error occurs
	 */
	boolean checkServerResponseAuth(byte[] serverResponse) throws SaslException {
		char[] response;
		ResponseAuth responseAuth = null;
		String responseStr;

		responseAuth = new ResponseAuth(serverResponse);

		response = DigestCalcResponse(m_HA1, m_dc.getNonce(), "00000001",
				m_clientNonce, m_qopValue, DIGEST_METHOD, m_digestURI, false);

		responseStr = new String(response);

		return responseStr.equals(responseAuth.getResponseValue());
	}

	/**
	 * This function returns hex character representing the value of the input
	 * 
	 * @param value
	 *            Input value in byte
	 * 
	 * @return Hex value of the Input byte value
	 */
	private static char getHexChar(byte value) {
		switch (value) {
		case 0:
			return '0';
		case 1:
			return '1';
		case 2:
			return '2';
		case 3:
			return '3';
		case 4:
			return '4';
		case 5:
			return '5';
		case 6:
			return '6';
		case 7:
			return '7';
		case 8:
			return '8';
		case 9:
			return '9';
		case 10:
			return 'a';
		case 11:
			return 'b';
		case 12:
			return 'c';
		case 13:
			return 'd';
		case 14:
			return 'e';
		case 15:
			return 'f';
		default:
			return 'Z';
		}
	}

	/**
	 * Calculates the Nonce value of the Client
	 * 
	 * @return Nonce value of the client
	 * 
	 * @exception SaslException
	 *                If an error Occurs
	 */
	String getClientNonce() throws SaslException {
		byte[] nonceBytes = new byte[NONCE_BYTE_COUNT];
		SecureRandom prng;
		byte nonceByte;
		char[] hexNonce = new char[NONCE_HEX_COUNT];

		try {
			prng = SecureRandom.getInstance("SHA1PRNG");
			prng.nextBytes(nonceBytes);
			for (int i = 0; i < NONCE_BYTE_COUNT; i++) {
				// low nibble
				hexNonce[i * 2] = getHexChar((byte) (nonceBytes[i] & 0x0f));
				// high nibble
				hexNonce[(i * 2) + 1] = getHexChar((byte) ((nonceBytes[i] & 0xf0) >> 4));
			}
			return new String(hexNonce);
		} catch (NoSuchAlgorithmException e) {
			throw new SaslException("No random number generator available", e);
		}
	}

	/**
	 * Returns the IANA-registered mechanism name of this SASL client. (e.g.
	 * "CRAM-MD5", "GSSAPI")
	 * 
	 * @return "DIGEST-MD5"the IANA-registered mechanism name of this SASL
	 *         client.
	 */
	public String getMechanismName() {
		return "DIGEST-MD5";
	}

} // end class DigestMD5SaslClient

